package de.hauschild.dbc4j.aspects;

import java.lang.reflect.Method;
import java.util.Set;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import de.hauschild.dbc4j.ContractViolationException;
import de.hauschild.dbc4j.annotation.Contracted;
import de.hauschild.dbc4j.annotation.PostCondition;
import de.hauschild.dbc4j.engine.ContractEngine;
import de.hauschild.dbc4j.engine.ContractEngineUtils;
import de.hauschild.dbc4j.util.JoinPointUtils;

/**
 * The aspect matching and evaluating all {@link PostCondition PostConditions} at a {@link Contracted} class.
 * 
 * @since 1.0.0
 * @author Klaus Hauschild
 */
@Aspect
public class PostConditionAspect extends AbstractConditionAspect {

  private PostConditionAspect() {
  }

  // @After("(execution (((@de.hauschild.dbc4j.annotation.Contracted *)+).new(..)))")
  public void constructors(final JoinPoint joinPoint) throws ContractViolationException {
    // TODO implement
  }

  @Around("execution (* ((@de.hauschild.dbc4j.annotation.Contracted *)+).*(..))")
  public Object methods(final ProceedingJoinPoint joinPoint) throws Throwable {
    // get method
    final Method method = JoinPointUtils.getMethod(joinPoint);
    // get postconditions for method
    final Set<PostCondition> postConditions = getAnnotationFrom(method, PostCondition.class);
    // proceed join point
    Object result = null;
    Throwable thrown = null;
    try {
      result = joinPoint.proceed();
    } catch (final Throwable throwable) {
      thrown = throwable;
    }
    // check each postconditions
    for (final PostCondition postCondition : postConditions) {
      // determine condition engine
      final ContractEngine engine = ContractEngineUtils.getContractEngine(postCondition);
      // evaluate postconditions
      if (!engine.evaluate(postCondition, joinPoint.getTarget(), joinPoint.getArgs(), result, thrown)) {
        // condition violated -> exception
        // TODO more information about violation
        throw new ContractViolationException(String.format("The postcondition '%s' was violated.", postCondition
            .value()));
      }
      // contract hold
    }
    // return
    if (thrown == null) {
      // no exception caught -> return result
      return result;
    } else {
      // re-throw exception
      throw thrown;
    }
  }

  public void staticMethods(final JoinPoint joinPoint) throws ContractViolationException {
    // TODO implement
  }

}
